package solver;


import ilog.concert.IloException;
import ilog.concert.IloLinearNumExpr;
import ilog.concert.IloNumVar;
import ilog.concert.IloObjective;
import ilog.cplex.IloCplex;
import problem.SingleWareHouseProblem;
import tools.Vector;
import uncertainty.UncertaintySet;
import util.Memorier;
import util.Timer;

public abstract class Solver {
	
	protected Memorier mem;
	protected Timer timer;
	protected SingleWareHouseProblem prob;
	protected UncertaintySet us;
	protected int numrest;
	protected int numvar;
	protected IloCplex cplex;
	protected VariableMapping x;
	protected VariableMapping s;
	protected VariableMapping r;
	protected IloNumVar kappa;
	protected int dim;
	protected int k;
	
	public Solver(SingleWareHouseProblem p, UncertaintySet s, int k) throws IloException {
		mem = new Memorier();
		timer = new Timer();
		numvar = 0;
		numrest = 0;
		prob = p;
		us = s;
		cplex = new IloCplex();
		dim = p.getDim();
		this.k = k;
		kappa = cplex.numVar(Double.MIN_VALUE, Double.MAX_VALUE);
	}
	
	protected void addInitialStock(Vector<Double> xi, double init) throws IloException{
		IloNumVar[] vars;
		IloLinearNumExpr exp =  cplex.linearNumExpr();
		vars = s.getVariableFor(xi, 0);
		exp.addTerm(1.0, vars[0]);
		for(int j = 1; j < vars.length; j++)exp.addTerm(xi.get(j-1), vars[j]);
		cplex.addEq(exp, init);
	}
	
	protected void positiveStock(Vector<Double> xi, int i) throws IloException{
		IloNumVar[] vars;
		IloLinearNumExpr exp =  cplex.linearNumExpr();
		vars = s.getVariableFor(xi, i);
		exp.addTerm(1.0, vars[0]);
		for(int j = 1; j < vars.length; j++)exp.addTerm(xi.get(j-1), vars[j]);
		cplex.addGe(exp, 0.0);
	}
	
	protected void positiveBacklog(Vector<Double> xi, int i) throws IloException{
		IloNumVar[] vars;
		IloLinearNumExpr exp =  cplex.linearNumExpr();
		vars = r.getVariableFor(xi, i);
		exp.addTerm(1.0, vars[0]);
		for(int j = 1; j < vars.length; j++)exp.addTerm(xi.get(j-1), vars[j]);
		cplex.addGe(exp, 0.0);
	}
	
	protected void positiveDemands(Vector<Double> xi, int i) throws IloException{
		IloNumVar[] vars;
		IloLinearNumExpr exp =  cplex.linearNumExpr();
		vars = x.getVariableFor(xi, i);
		exp.addTerm(1.0, vars[0]);
		for(int j = 1; j < vars.length; j++)exp.addTerm(xi.get(j-1), vars[j]);
		cplex.addGe(exp, 0.0);		
	}	
	
	protected void addBuyCapacities(Vector<Double> xi, int i, double limit) throws IloException{
		IloNumVar[] vars;
		IloLinearNumExpr exp =  cplex.linearNumExpr();
		vars = x.getVariableFor(xi, i);
		exp.addTerm(1.0, vars[0]);
		for(int j = 1; j < vars.length; j++)exp.addTerm(xi.get(j-1), vars[j]);
		cplex.addLe(exp, limit);
	}
	
	protected void addStorageCapacities(Vector<Double> xi, int i, double limit) throws IloException{
		IloNumVar[] vars;
		IloLinearNumExpr exp =  cplex.linearNumExpr();
		vars = s.getVariableFor(xi, i);
		exp.addTerm(1.0, vars[0]);
		for(int j = 1; j < vars.length; j++)exp.addTerm(xi.get(j-1), vars[j]);
		cplex.addLe(exp, limit);		
	}	
	
	protected void addShortageCapacities(Vector<Double> xi, int i, double limit) throws IloException{
		IloNumVar[] vars;
		IloLinearNumExpr exp =  cplex.linearNumExpr();
		vars = r.getVariableFor(xi, i);
		exp.addTerm(1.0, vars[0]);
		for(int j = 1; j < vars.length; j++)exp.addTerm(xi.get(j-1), vars[j]);
		cplex.addLe(exp, limit);
	}
	
	protected void addMainConstraint(Vector<Double> xi) throws IloException{
		IloNumVar[] vars;
		IloLinearNumExpr exp =  cplex.linearNumExpr();
		Vector<Double> cost;
		
		//demand cost
		for(int i  = 0; i < dim; i++){
			vars = x.getVariableFor(xi, i);
			cost = prob.getDemandCosts();
			exp.addTerm(-cost.get(i), vars[0]);
			for(int j = 1; j < vars.length; j++)exp.addTerm(-cost.get(i)*xi.get(j-1), vars[j]);
		}
		
		//storage cost
		for(int i  = 0; i < dim; i++){
			vars = s.getVariableFor(xi, i);
			cost = prob.getStorageCosts();
			exp.addTerm(-cost.get(i), vars[0]);
			for(int j = 1; j < vars.length; j++)exp.addTerm(-cost.get(i)*xi.get(j-1), vars[j]);
		}
		
		//backlog cost
		if(r != null){
			for(int i  = 0; i < dim; i++){
				vars = r.getVariableFor(xi, i);
				cost = prob.getShortageCosts();
				exp.addTerm(-cost.get(i), vars[0]);
				for(int j = 1; j < vars.length; j++)exp.addTerm(-cost.get(i)*xi.get(j-1), vars[j]);
			}
		}
			
		exp.addTerm(1, kappa);	
		cplex.addGe(exp, 0.0);	
	}
	
	protected void addPeriodConstrain(Vector<Double> xi, int i) throws IloException{
		IloNumVar[] vars;
		IloLinearNumExpr exp =  cplex.linearNumExpr();
		
		vars = x.getVariableFor(xi, i);
		exp.addTerm(-1.0, vars[0]);
		for(int j = 1; j < vars.length; j++)exp.addTerm(-1.0*xi.get(j-1), vars[j]);
		vars = null;
		
		vars = s.getVariableFor(xi, i);
		exp.addTerm(1.0, vars[0]);
		for(int j = 1; j < vars.length; j++)exp.addTerm(1.0*xi.get(j-1), vars[j]);
		vars = null;
		
		if(i != 0){	
			vars = s.getVariableFor(xi, i-1);
			exp.addTerm(-1.0, vars[0]);
			for(int j = 1; j < vars.length; j++)exp.addTerm(-1.0*xi.get(j-1), vars[j]);
			vars = null;
		}
		
		if(r!=null){
			vars = r.getVariableFor(xi, i);
			exp.addTerm(-1.0, vars[0]);
			for(int j = 1; j < vars.length; j++)exp.addTerm(-1.0*xi.get(j-1), vars[j]);
			
			if(i != dim-1){
				vars = r.getVariableFor(xi, i+1);
				exp.addTerm(1.0, vars[0]);
				for(int j = 1; j < vars.length; j++)exp.addTerm(1.0*xi.get(j-1), vars[j]);
			}

		}
		
		cplex.addGe(exp, xi.get(i));
	}
	
	public abstract boolean build() throws IloException;
	
	public double solve() throws IloException{
		timer.start();
		IloLinearNumExpr obj = cplex.linearNumExpr();
		obj.addTerm(1.0, kappa);
		IloObjective sobj = cplex.minimize(obj);
		cplex.add(sobj);
		if(!cplex.solve()) System.out.println("Problems with the Solution!");
		cplex.exportModel("model.lp");
		timer.stop();
		return cplex.getObjValue();
	}

	public double memoryUsedB(){
		double m = mem.getUsedMemB();
		return m;
	}
	
	public double memoryUsedKB(){
		double m = mem.getUsedMemKB();
		return m;
	}
	
	public double memoryUsedMB(){
		double m = mem.getUsedMemMB();
		return m;
	}
	
	public double timeUsedMiliSec(){
		return timer.getTimeMiliSec();
	}
	
	public double timeUsedSec(){
		return timer.getTimeSec();
	}
	
	public int getRestNumber(){
		return numrest;
	}
	
	public int getVarNumber(){
		return numvar;
	}
	
	public double getTime() {
		return timer.getTimeSec();
	}
	
	public void finalize() throws Throwable{
		x.finalize();
		s.finalize();
		if(r!=null)r.finalize();
		cplex.clearModel();
		cplex = null;
		prob.finalize();
		System.gc();
	}
}
